﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.NRefactory;
using Gendarme.Rules.Smells;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.PrettyPrinter;

namespace Commander.Core.Refactorings.Modifiers {
	public class SameClassDuplicationModifier : NetWalkerCodeModifier {

		SameClassDuplicationExtra m_extra;
		bool m_isMethodCreated = false;
		MethodDeclaration m_genMeth;
		TypeDeclaration m_type;

		string methName;
		string GeneratedMethodName {
			get {
				if (string.IsNullOrEmpty(methName)) {
          methName = GenId;
				}
				return methName;
			}
		}

		public SameClassDuplicationModifier(XElement extra, IParser parser)
			: base(extra, parser) {
			m_extra = SameClassDuplicationDeserializer.Deserialize(extra);
		}

		public override string ApplyModification() {
			CompilationUnit cu = m_parser.CompilationUnit;
			cu.AcceptVisitor(this, null);

			m_type.AddChild(m_genMeth);

			IOutputAstVisitor output = new CSharpOutputVisitor();
			cu.AcceptVisitor(output, null);
			//m_type = null;
			//m_genMeth = null;
			return output.Text;
		}

		public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) {
			base.VisitMethodDeclaration(methodDeclaration, data);

			//if(methodDeclaration.Parent.
			if ((methodDeclaration.Parent as TypeDeclaration).Name.Equals(m_extra.AffectedClassName)) {				
				//bool isMethodCallInserted = false;

				if (methodDeclaration.Name.Equals(m_extra.Methods.MethodPairs.Key)) {
					var stmts = methodDeclaration.Body.Children;
					if (!m_isMethodCreated) {
						CreateMethod(methodDeclaration.Parent as TypeDeclaration, stmts);
						m_isMethodCreated = true;
					}
					foreach (var matches in m_extra.Methods.MatchedStatements) {
						if (stmts.Count > matches.Key) {
							stmts[matches.Key] = null;
						}
					}
					int insertionIndex = (m_extra.Methods.MatchedStatements[0].Key - 1 >= 0) ? m_extra.Methods.MatchedStatements[0].Key - 1 : 0;
					stmts.Insert(insertionIndex, CreateInvokeCall(GeneratedMethodName));
					RemoveNullStatements(stmts);
				}
				else if (methodDeclaration.Name.Equals(m_extra.Methods.MethodPairs.Value)) {
					var stmts = methodDeclaration.Body.Children;
					foreach (var matches in m_extra.Methods.MatchedStatements) {
						if (stmts.Count > matches.Value) {
							stmts[matches.Value] = null;
						}
					}
					int insertionIndex = (m_extra.Methods.MatchedStatements[0].Value - 1 >= 0) ? m_extra.Methods.MatchedStatements[0].Value - 1 : 0;
					stmts.Insert(insertionIndex, CreateInvokeCall(GeneratedMethodName));
					RemoveNullStatements(stmts);
				}
				else {
				}
			}
			return null;
		}

		private ExpressionStatement CreateInvokeCall(string methName) {
			ThisReferenceExpression ident = new ThisReferenceExpression();
			MemberReferenceExpression methodName = new MemberReferenceExpression(ident, methName);
			InvocationExpression ie = new InvocationExpression(methodName, null);
			return new ExpressionStatement(ie);
		}

		private void CreateMethod(TypeDeclaration typeDeclaration, IList<INode> nodes) {
			m_type = typeDeclaration;
			m_genMeth = new MethodDeclaration();
			m_genMeth.Name ="void " + GeneratedMethodName;
			m_genMeth.Body = new BlockStatement();
			//NullBlockStatement p;
			foreach (var matches in m_extra.Methods.MatchedStatements) {
				//newMeth.Body.a
				if (nodes.Count > matches.Key) {
					m_genMeth.Body.AddChild(nodes[matches.Key]);
				}
			}

		}

		private void RemoveNullStatements(List<INode> stmts) {
			for (int i = 0; i < stmts.Count; i++) {
				if (stmts[i] == null) {
					stmts.RemoveAt(i);
					i--;
				}
			}
		}

	}

	class SameClassDuplicationDeserializer {

		internal static SameClassDuplicationExtra Deserialize(XElement extra) {
			var deser = new SameClassDuplicationExtra { AffectedClassName = extra.Attribute(CommanderSerializer.NAME).Value };
			var meth = new AffectedMethod();

			foreach (var affected in extra.Elements()) {
				var aff1 = affected.Elements().ElementAt(0);
				var aff2 = affected.Elements().ElementAt(1);
				if (meth.MethodPairs.Key == null) {
					meth.MethodPairs = new KeyValuePair<string, string>(aff1.Attribute(CommanderSerializer.ORIGIN).Value,
																															aff2.Attribute(CommanderSerializer.ORIGIN).Value);
					meth.MatchedStatements = new List<KeyValuePair<int, int>>();
				}

        meth.MatchedStatements.Add(new KeyValuePair<int, int>(int.Parse(aff1.Attribute(CommanderSerializer.NUMBER).Value) + NetWalkerCodeModifier.Differantial,
                                                              int.Parse(aff2.Attribute(CommanderSerializer.NUMBER).Value) + NetWalkerCodeModifier.Differantial));
			}

			deser.Methods = meth;

			return deser;
		}


	}

	class SameClassDuplicationExtra {
		public string AffectedClassName { get; set; }
		public AffectedMethod Methods { get; set; }
	}

	class AffectedMethod {
		public KeyValuePair<string, string> MethodPairs { get; set; }
		public List<KeyValuePair<int, int>> MatchedStatements { get; set; }

		public void Normalize() {
			MatchedStatements.Sort(new StatementComparer());
			//Apply sequence checking to remove non sequential matches
		}
	}

	class StatementComparer : IComparer<KeyValuePair<int, int>> {
		public int Compare(KeyValuePair<int, int> x, KeyValuePair<int, int> y) {
			if (x.Key == y.Key) return 0;
			return (x.Key > y.Key) ? 1 : -1;
		}
	}
}
